<?php
/**
 * lib/Interpreter.php 
 * 
 * @package general
 * @subpackage 
 * @copyright 2010, (c) Redcore Studio
 * @author Pablo López Torres <pablolopeztorres@gmail.com> 
 * @license 
 */

/**
 * Interpreter 
 * 
 * @author Pablo López Torres <pablolopeztorres@gmail.com> 
 */
class Interpreter
{
}


/**
 * This is as Dynamic Language Specification interpreter 
 * 
 * @author Pablo López Torres <pablolopeztorres@gmail.com> 
 */
class DlsInterpreter
{
    public function convertDlsToText(DlsElement $dlsElement)
    {
        switch (get_class($dlsElement))
        {
            case 'DlsToken':
                $text = $dlsElement->getName();
                break;
            case 'DlsGroup':
                // get the pieces of text
                $textChunks = array();
                foreach ($dlsElement->getElements() as $childElement)
                {
                    $textChunks[] = $this->convertDlsToText($childElement);
                }

                // get the operator
                switch ($dlsElement->getType())
                {
                    case DlsGroup::TYPE_NONE:
                    case DlsGroup::TYPE_ADITTION:
                        $operator = ' + ';
                        break;
                    case DlsGroup::TYPE_EXCLUSION:
                        $operator = ' | ';
                        break;
                    case DlsGroup::TYPE_COMMUTATION:
                        $operator = ' & ';
                        break;
                }

                // start compiling the text
                $text = '[ ';
                $name = $dlsElement->getName();
                if ($name !== NULL)
                {
                    $text .= "= $name # ";
                }
                switch ($dlsElement->getCounter())
                {
                    case DlsGroup::COUNTER_NONE:
                        break;
                    case DlsGroup::COUNTER_NONE_OR_ONE:
                        $text .= '? ';
                        break;
                    case DlsGroup::COUNTER_NONE_OR_MORE:
                        $text .= '* ';
                        break;
                    case DlsGroup::COUNTER_ONE_OR_MORE:
                        $text .= '+ ';
                        break;
                }
                $text .= implode($operator, $textChunks);
                $text .= ' ]';
                
                break;
            default:
                throw new DlsException("ERROR: Unknown element");
                break;
        }

        return $text;
    }

    public function convertFromText($text)
    {
        // tokens DLS
        $tokens = preg_split('/[ \t\n]+/m', trim($text));
        return $this->convertFromTokens($tokens);
    }

    /**
     * Creates the SDL structure from a text definition.
     * 
     * Note everytime this method is called the first token should be a '[' and
     * it should give the return when the matching ']' is found.
     * 
     * @access public
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @param mixed $text 
     * @return void
     */
    public function convertFromTokens(&$tokens)
    {
        $token = array_shift($tokens);

        if ($token !== '[')
        {
            throw new DlsException('Unknown error when the parse was started');
        }

        $elements = array();
        $groupType = DlsGroup::TYPE_NONE;
        $groupName = NULL;
        $groupCounter = DlsGroup::COUNTER_NONE;
        
        // parse tokens
        $token = array_shift($tokens);
        while ($token !== ']')
        {
            switch ($token)
            {
                case '[':
                    array_unshift($tokens, $token);
                    $elements[] = $this->convertFromTokens($tokens);
                    break;
                // GROUP TYPES
                case '+':
                    $groupType = DlsGroup::TYPE_ADITTION;
                    break;
                case '|':
                    $groupType = DlsGroup::TYPE_EXCLUSION;
                    break;
                case '&':
                    $groupType = DlsGroup::TYPE_COMMUTATION;
                    break;
                // GROUP MODIFICATION PARAMS
                case '*':
                    // @todo assert no other modification type is already set
                    $groupCounter = DlsGroup::COUNTER_NONE_OR_MORE;
                    break;
                case '+':
                    // @todo assert no other modification type is already set
                    $groupCounter = DlsGroup::COUNTER_ONE_OR_MORE;
                    break;
                case '?':
                    // @todo assert no other modification type is already set
                    $groupCounter = DlsGroup::COUNTER_NONE_OR_ONE;
                    break;
                case '#':
                    // just ignore it
                    break;
                case '=':
                    // @todo assure the next token is a valid name
                    $token = array_shift($tokens);
                    $groupName = $token;
                    break;
                default:
                    $elements[] = new DlsToken($token);
                    break;
            }
            $token = array_shift($tokens);
        }

        // create group
        $group = DlsFactory::get()->getGroup($groupType, $groupCounter);
        $group->setName($groupName);

        foreach ($elements as $element)
        {
            $group->addElement($element);
        }

        return $group;
    }

    public function detect(DlsElement $pattern, TokenList $tokens)
    {
        $nodeMap = $pattern->getNodeMap();
        print_r($nodeMap);
    }
}


/**
 * DlsFactory 
 * 
 * @final
 * @author Pablo López Torres <pablolopeztorres@gmail.com> 
 */
class DlsFactory
{
    final protected function __construct()
    {
        
    }

    private static $instance;

    public static function get()
    {
        if (self::$instance === NULL)
        {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function getGroup($type, $counter)
    {
        switch ($type)
        {
            case DlsGroup::TYPE_ADITTION:
                $group = new DlsAdittionGroup($counter);
                break;
            case DlsGroup::TYPE_EXCLUSION:
                $group = new DlsExclusionGroup($counter);
                break;
            case DlsGroup::TYPE_COMMUTATION:
                $group = new DlsCommutationGroup($counter);
                break;
            default:
                $group = new DlsGroup($counter);
                break;
        }

        $group->setCounter($counter);

        return $group;
    }

}

class DlsAdittionGroup extends DlsGroup
{
    public function __construct()
    {
        parent::__construct();
        $this->type = self::TYPE_ADITTION;
    }
    public function addElement(DlsElement $element)
    {
        parent::addElement($element);
    }

    public function getNodeMap()
    {
        var_dump($this);
        throw new Exception('NOT IMPLEMENTED');
    }
}
class DlsExclusionGroup extends DlsGroup
{
    public function __construct()
    {
        parent::__construct();
        $this->type = self::TYPE_EXCLUSION;
    }
    public function addElement(DlsElement $element)
    {
        parent::addElement($element);
    }

    public function getNodeMap()
    {
        print_r($this);
        print_r($this->getCounter());

        $nodeMap = new NodeMap();

        print_r($nodeMap);

        switch ($this->getCounter())
        {
            /*
             *  I --> O
             */
            case DlsGroup::COUNTER_NONE:
                // the empty nodeMap needs to be welded
                $bridgeLeft = new UnidirectionalNode(new NodeDlsBridge());
                $bridgeRight = new UnidirectionalNode(new NodeDlsBridge());

                // weld the nodes
                $bridgeLeft->addOutput($bridgeRight);

                $nodeMap->addInput($bridgeLeft);
                $nodeMap->addOutput($bridgeRight);

                break;
            /*
             *    - T -
             *   /     \
             *  I - T - O
             *   \     /
             *    - T -
             */
            case DlsGroup::COUNTER_ONE:
                // the empty nodeMap will be filled with tokens
                $bridgeLeft = new UnidirectionalNode(new NodeDlsBridge());
                $bridgeRight = new UnidirectionalNode(new NodeDlsBridge());
                
                foreach ($this->getElements() as $element)
                {
                    $node = new UnidirectionalNode();
                    $bridgeLeft->addOutput($node);
                    $node->addOutput($bridgeRight);
                }

                $nodeMap->addInput($bridgeLeft);
                $nodeMap->addOutput($bridgeRight);

                break;
            /*
             *    - T -
             *   /     \
             *  I ----> O
             *  |\     /|
             *   \- T -/
             *    - T -
             */
            case DlsGroup::COUNTER_NONE_OR_ONE:
                
                // the empty nodeMap will be welded at the end
                $bridgeLeft = new UnidirectionalNode(new NodeDlsBridge());
                $bridgeRight = new UnidirectionalNode(new NodeDlsBridge());
                
                foreach ($this->getElements() as $element)
                {
                    $node = new UnidirectionalNode();
                    $bridgeLeft->addOutput($node);
                    $node->addOutput($bridgeRight);
                }
                $bridgeLeft->addOutput($bridgeRight);

                $nodeMap->addInput($bridgeLeft);
                $nodeMap->addOutput($bridgeRight);

                break;
            /*
             *    - T -
             *   /     \
             *  I ----> O ---> I
             *  |\     /|
             *   \- T -/
             *    - T -
             */
            case DlsGroup::COUNTER_NONE_OR_MORE:

                // the empty nodeMap will be welded at the end
                $bridgeLeft = new UnidirectionalNode(new NodeDlsBridge());
                $bridgeRight = new UnidirectionalNode(new NodeDlsBridge());
                
                foreach ($this->getElements() as $element)
                {
                    $node = new UnidirectionalNode();
                    $bridgeLeft->addOutput($node);
                    $node->addOutput($bridgeRight);
                }
                $bridgeLeft->addOutput($bridgeRight);
                $bridgeRight->addOutput($bridgeLeft);

                $nodeMap->addInput($bridgeLeft);
                $nodeMap->addOutput($bridgeRight);

                break;
            /*
             *    - T -
             *   /     \
             *  I - T - O ---> I
             *   \     /
             *    - T -
             *
             */
            case DlsGroup::COUNTER_ONE_OR_MORE:

                $bridgeLeft = new UnidirectionalNode(new NodeDlsBridge());
                $bridgeRight = new UnidirectionalNode(new NodeDlsBridge());
                
                foreach ($this->getElements() as $element)
                {
                    $node = new UnidirectionalNode();
                    $bridgeLeft->addOutput($node);
                    $node->addOutput($bridgeRight);
                }
                $bridgeRight->addOutput($bridgeLeft);

                $nodeMap->addInput($bridgeLeft);
                $nodeMap->addOutput($bridgeRight);

                break;
        }

        return $nodeMap;
    }
}
class DlsCommutationGroup extends DlsGroup
{
    public function __construct()
    {
        parent::__construct();
        $this->type = self::TYPE_COMMUTATION;
    }
    public function addElement(DlsElement $element)
    {
        parent::addElement($element);
    }
    public function getNodeMap()
    {
        var_dump($this);
        throw new Exception('NOT IMPLEMENTED');
    }
}


/**
 * DlsElement 
 * 
 * @author Pablo López Torres <pablolopeztorres@gmail.com> 
 */
abstract class DlsElement
{
    public function __construct()
    {
        $this->id = spl_object_hash($this);
    }
}

/**
 * DlsToken 
 * 
 * @uses DlsElement
 * @author Pablo López Torres <pablolopeztorres@gmail.com> 
 */
class DlsToken extends DlsElement
{
    private $name;

    public function __construct($name = NULL)
    {
        parent::__construct();
        $this->setName($name);
    }

    public function getName()
    {
        if (! isset($this->name))
        {
            throw new DlsException("ERROR: Token must have a name");
        }
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }

}

/**
 * Represents a SDL group.
 * 
 * It could be from three types
 * 
 * @abstract
 * @uses Container
 * @author Pablo López Torres <pablolopeztorres@gmail.com> 
 */
abstract class DlsGroup extends DlsElement
{
    const TYPE_NONE = 0;
    const TYPE_ADITTION = 1;
    const TYPE_EXCLUSION = 2;
    const TYPE_COMMUTATION = 3;

    const COUNTER_NONE = 0;
    const COUNTER_NONE_OR_ONE = 1;
    const COUNTER_NONE_OR_MORE = 2;
    const COUNTER_ONE_OR_MORE = 3;

    private $id = NULL;
    private $type = self::TYPE_NONE;
    private $counter = self::COUNTER_NONE;
    private $name = NULL;
    private $elements = array();

    public function __construct()
    {
        parent::__construct();
    }

    public function getType()
    {
        return $this->type;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }

    public function getCounter()
    {
        return $this->counter;
    }

    public function setCounter($counter)
    {
        $this->counter = $counter;
    }

    public function getElements()
    {
        return $this->elements;
    }
    public function addElement(DlsElement $element)
    {
        $this->elements[] = $element;
    }

    abstract public function getNodeMap();
}


class NodeMap
{
    private $inputs = array();
    private $outputs = array();

    public function __construct()
    {
    }

    public function addInput(Node $node) {
        // @todo more than one input
        assert('count($this->inputs) === 0');
        $this->inputs[] = $node;
    }

    public function addOutput(Node $node) {
        // @todo more than one output
        assert('count($this->outputs) === 0');
        $this->outputs[] = $node;
    }


    public function getInput()
    {
        return reset($this->inputs);
    }
    public function getOutputs()
    {
        return reset($this->outputs);
    }
}

class InvalidNodeIdentifier extends Exception
{

}


class Node
{
    private $nodeElement = NULL;

    public function __construct(NodeElement $nodeElement = NULL)
    {
        $this->nodeElement = $nodeElement;
    }

    public function setElement(NodeElement $nodeElement)
    {
        $this->nodeElement = $nodeElement;
    }

}

class UnidirectionalNode extends Node
{
    private $outputs = array();

    public function addOutput(Node $node) {
        $this->outputs[] = $node;
    }
    public function getOutputs() {
        return $this->outputs;
    }
}

class BidirectionalNode extends UnidirectionalNode
{
    private $inputs = array();

    public function addInput(Node $node) {
        $this->inputs[] = $node;
    }
    public function getInputs() {
        return $this->inputs;
    }
    public function addOutput(Node $node)
    {
        parent::addOutput($node);
        $node->addInput($this);
    }
}


class NodeElement
{

}

class NodeDlsTokenMatcher extends NodeElement
{
    private $token;

    public function __construct($token)
    {
        $this->token = $token;
    }

    public function match(TokenList $tokens)
    {
        $matching = FALSE;
        $token = $tokens->current();
        if ($this->token === $token[Tokenizer::POSITION_ID])
        {
            $matching = TRUE;
            $tokens->next();
        }
        return $matching;
    }
}

class NodeDlsBridge extends NodeElement
{
    public function match(TokenList $tokens)
    {
        return TRUE;
    }
}


$text = '[
    [
        [ = ACCESS_TYPE # T_PUBLIC | T_PRIVATE | T_PROTECTED ] 
        & T_ABSTRACT
        & T_FINAL
    ] 
    + T_CLASS 
    + [ = CLASS_NAME # T_STRING ]
        + [ ? = INHERITANCE # T_EXTENDS + T_STRING ] 
        + [ ? = INTERFACES # T_IMPLEMENTS + T_STRING + [ * T_COMMA + T_STRING ] ] 
    + T_BRACKET
]';

require_once 'lib/Tokenizer.php';

$text = '[ * T_PUBLIC | T_ABSTRACT ]';

$sourcecode = '<?php abstract private class Demo {';
$tokens = Tokenizer::prepare($sourcecode, array('exclusions' => array( T_OPEN_TAG, T_CLOSE_TAG, T_WHITESPACE, T_COMMENT )));
// var_dump($tokens);

$dls = new DlsInterpreter();

$pattern = $dls->convertFromText($text);
// print_r($pattern);

var_dump($dls->detect($pattern, $tokens));

die();

// print $dls->convertDlsToText($dls->convertFromText($dls->convertDlsToText($pattern)));

$sourcecode = '<?php abstract private class Demo {';

$tokens = Tokenizer::prepare($sourcecode, array('exclusions' => array( T_OPEN_TAG, T_CLOSE_TAG, T_WHITESPACE, T_COMMENT )));

foreach ($tokens as $token)
{
    var_dump(Tokenizer::getConstantName($token[0]));
}

print $dls->detect($pattern, $tokens);

